// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "check.h"
#include "connection.h"
#include "output_print_job.h"
#include "hashmap.h"
#include "http.h"
#include "net.h"


struct BufReader {
  struct Connection *conn;
  int               pos, len;
  char              buf[4096];
};

static void initBufReader(struct BufReader *reader, struct Connection *conn) {
  reader->conn = conn;
  reader->pos  = 0;
  reader->len  = 0;
}

static struct BufReader *newBufReader(struct Connection *conn) {
  struct BufReader *reader;
  check(reader = malloc(sizeof(struct BufReader)));
  initBufReader(reader, conn);
  return reader;
}

static void destroyBufReader(struct BufReader *reader) {
}

static void deleteBufReader(struct BufReader *reader) {
  destroyBufReader(reader);
  free(reader);
}

static size_t fillBufReader(struct BufReader *reader) {
  size_t avail = sizeof(reader->buf) - reader->len;
  if (avail <= 0 && reader->pos) {
    // No more space at the end of the buffer, but there might be space that
    // we have already consumed and can now recycle. We don't do full ring-
    // buffer, as that is typically not needed for what we do.
    memmove(reader->buf, reader->buf + reader->pos, reader->len - reader->pos);
    reader->len -= reader->pos;
    reader->pos  = 0;
    avail        = sizeof(reader->buf) - reader->len;
  }
  if (avail > 0) {
    ssize_t nbytes = connRead(reader->conn, reader->buf+reader->len, avail);
    if (nbytes > 0) {
      reader->len += nbytes;
    }
    return nbytes;
  }
  return 0;
}

static void consumeBufReader(struct BufReader *reader, size_t sz) {
  reader->pos += sz;
  if (reader->len == reader->pos) {
    reader->len = 0;
    reader->pos = 0;
  }
}

static int getCharBufReader(struct BufReader *reader) {
  if (reader->pos < reader->len ||
      fillBufReader(reader) > 0) {
    return (unsigned char)reader->buf[reader->pos++];
  }
  return -1;
}

void outputPrintJob(struct OAuth2 *oauth2, struct JSON *job,
                    struct PrinterBackend *backend) {
  check(job->type == J_OBJECT);
  const struct JSON *urlObj, *titleObj;
  check(urlObj = (struct JSON *)getFromHashMap(&job->obj, "fileUrl"));
  check(urlObj->type == J_STRING);
  check(titleObj = (struct JSON *)getFromHashMap(&job->obj, "title"));
  check(titleObj->type == J_STRING);
  struct Connection cloudprint;
  check(openHTTPSocket(&cloudprint, "GET", urlObj->str,
                       "Accept: image/pwg-raster\r\n",
                       oauth2, NULL, M_MULTIPART) == 200);
  struct BufReader *reader = newBufReader(&cloudprint);
  const ssize_t pwg_header_sz = 1796;
  check(pwg_header_sz <= sizeof(reader->buf));
  while (reader->len < 4) {
    check(fillBufReader(reader) > 0);
  }
  check(!memcmp(reader->buf, "RaS2", 4));
  consumeBufReader(reader, 4);
  void *arg = NULL;
  for (struct PrintParams lastParams = { 0 };;) {
    if (reader->pos == reader->len && !fillBufReader(reader)) {
      break;
    }
    while ((reader->len - reader->pos) < pwg_header_sz) {
      check(fillBufReader(reader) > 0);
    }
    char *buf        = reader->buf + reader->pos;
    struct PrintParams params = { 0 };
    params.hRes         = ntohl(*(int *)(buf + 276));
    params.vRes         = ntohl(*(int *)(buf + 280));
    params.leadingEdge  = ntohl(*(int *)(buf + 308));
    params.width        = ntohl(*(int *)(buf + 372));
    params.height       = ntohl(*(int *)(buf + 376));
    params.bitsPerColor = ntohl(*(int *)(buf + 384));
    params.bitsPerPixel = ntohl(*(int *)(buf + 388));
    params.bytesPerLine = ntohl(*(int *)(buf + 392));
    params.colorSpace   = ntohl(*(int *)(buf + 400));
    params.numColors    = ntohl(*(int *)(buf + 420));
    consumeBufReader(reader, pwg_header_sz);
    check(params.hRes > 0 && params.hRes < 10000);
    check(params.vRes > 0 && params.vRes < 10000);
    check(params.leadingEdge == 0 || params.leadingEdge == 1);
    check(params.width > 0 && params.width < 100000);
    check(params.height > 0 && params.height < 100000);
    check(params.bitsPerColor == 1 || params.bitsPerColor == 8 ||
          params.bitsPerColor == 16);
    check(params.bitsPerPixel == params.bitsPerColor * params.numColors);
    check(params.bytesPerLine == (params.bitsPerPixel * params.width + 7)/8);
    check(params.colorSpace == CLSP_BLACK || params.colorSpace == CLSP_CMYK ||
          params.colorSpace == CLSP_SGRAY || params.colorSpace == CLSP_SRGB ||
          params.colorSpace == CLSP_ADOBE_RGB ||
          (params.colorSpace >= CLSP_VENDOR48 &&
           params.colorSpace <= CLSP_VENDOR62));
    check(params.numColors > 0 && params.numColors <= 15);
    check(params.bitsPerPixel == 1 || !(params.bitsPerPixel % 8));
    if (memcmp(&lastParams, &params, sizeof(struct PrintParams))) {
      if (arg) {
        check(!backend->newParams(arg, &params));
      } else {
        check(arg = backend->startJob(backend, &params));
      }
      lastParams = params;
    }
    check(!backend->startPage || !backend->startPage(arg));
    int w   = params.width;
    int bpp = params.bitsPerPixel/8;
    if (params.bitsPerPixel == 1) {
      w   = (w + 7)/8;
      bpp = 1;
    }
    char *line;
    check(line = malloc(params.bytesPerLine));
    for (int y = 0; y < params.height; ) {
      int repeatY = getCharBufReader(reader);
      check(repeatY >= 0);
      char *out = line;
      for (int x = 0; x < w; ) {
        int repeatX = getCharBufReader(reader);
        check(repeatX >= 0);
        check(repeatX != 128);
        if (repeatX < 128) {
          x += repeatX + 1;
          check(x <= w);
          char *pixel = out;
          for (int p = 0; p < bpp; ++p) {
            int ch = getCharBufReader(reader);
            check(ch >= 0);
            *out++ = ch;
          }
          while (repeatX--) {
            // Repeat the same color 1..128 times
            memcpy(out, pixel, bpp);
            out += bpp;
          }
        } else {
          repeatX = 257 - repeatX;
          x += repeatX;
          check(x <= w);
          while (repeatX--) {
            // 2..128 unrepeated pixels
            for (int p = 0; p < bpp; ++p) {
              int ch = getCharBufReader(reader);
              check(ch >= 0);
              *out++ = ch;
            }
          }
        }
      }
      check(out == line + params.bytesPerLine);
      for (; repeatY-- >= 0; ++y) {
        check(!backend->sendData(arg, line, params.bytesPerLine));
      }
    }
    free(line);
    check(!backend->finishPage || !backend->finishPage(arg));
  }
  check(!arg || !backend->finishJob || !backend->finishJob(arg));
  deleteBufReader(reader);
  destroyConnection(&cloudprint);
}

void destroyPrinterBackend(struct PrinterBackend *backend) {
  if (backend) {
    backend->destructor(backend);
  }
}

void deletePrinterBackend(struct PrinterBackend *backend) {
  destroyPrinterBackend(backend);
  free(backend);
}
